package crypt;


public class DES {

	private static int[] initialPerm = null;
	private static int[] initialPermInv = null;
	private static int[] expandPerm = null;
	
	
	private long[] keys = null;
	
	private long key = 0;
	private static final int roundCount = 16;
	private static final int msgLen = 64;
	
	private static final int[] permutationKey1 = new int[] { 56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, 9,
        1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21,
        13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3 };
	private static final int[] permutationKey2 = new int[] { 13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, 22, 18, 11, 3,
        25, 7, 15, 6, 26, 19, 12, 1, 40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47, 43, 48, 38, 55, 33, 52, 45,
        41, 49, 35, 28, 31 };
	private static final int[] finalPerm = new int[] { 15, 6, 19, 20, 28, 11, 27, 16, 0, 14, 22, 25, 4, 17, 30,
        9, 1, 7, 23, 13, 31, 26, 2, 8, 18, 12, 29, 5, 21, 10, 3, 24 };
	private static final int[][] sbox = new int[][] {
        { 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3,
                8, 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14,
                10, 0, 6, 13 },
        { 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11,
                5, 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12,
                0, 5, 14, 9 },
        { 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15,
                1, 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3,
                11, 5, 2, 12 },
        { 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14,
                9, 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11,
                12, 7, 2, 14 },
        { 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8,
                6, 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9,
                10, 4, 5, 3 },
        { 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3,
                8, 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7,
                6, 0, 8, 13 },
        { 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8,
                6, 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15,
                14, 2, 3, 12 },
        { 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9,
                2, 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0,
                3, 5, 6, 11 } };
	
	static {
		initialPerm = new int[msgLen];
		initialPerm[7] = 1;
        int i = 0, j = 7, pret = 7;
        for (int t = 1; t < 32; t++) {
            if (i < 3)
                i++;
            else {
                i = 0;
                j--;
            }
            initialPerm[i * 8 + j] = initialPerm[pret] + 2;
            pret = i * 8 + j;
        }
        for (i = 4; i < 8; i++)
            for (j = 0; j < 8; j++)
            	initialPerm[i * 8 + j] = initialPerm[(i - 4) * 8 + j] - 1;

        initialPermInv = new int[msgLen];
        for (int t = 0; t < msgLen; t++)
        	initialPermInv[initialPerm[t]] = t;

        expandPerm = new int[48];
        expandPerm[0] = 31;
        expandPerm[1] = 0;
        for (i = 2; i < 47; i++)
            if (i % 6 == 0)
            	expandPerm[i] = expandPerm[i - 1] - 1;
            else
            	expandPerm[i] = expandPerm[i - 1] + 1;
        expandPerm[47] = 0;
    }
	
	public DES(String k)
	{
		key = stringToLong(k);
		
		keys = new long[roundCount];
		long t = permute(key, permutationKey1, 64);
		long c = t >>> 28;
		long d = t & maskL(28);
		
		for(int i = 0; i < roundCount; ++i)
		{
			c = rotL(c, 28);
			d = rotL(d, 28);
			if((i != 0) && (i!= 1) && (i != 8) && (i != 15))
			{
				c = rotL(c, 28);
				d = rotL(d, 28);
			}
			keys[i] = permute((c << 28) | d, permutationKey2, 56);
		}
	}
	
	private static long rotL(long a, int bits)
	{
		return ((a << 1) & maskL(bits)) + bitAt(a, bits - 1);
	}
	
	private static long stringToLong(String txt)
	{
		long n = 0;
		for(int i = 0; i < txt.length(); ++i)
			n = (n << 1) | bitAt(txt, i);
		return n;
	}
	
	@SuppressWarnings("unused")
	private static long stringToInt(String txt)
	{
		int n = 0;
		for(int i = 0; i < txt.length(); ++i)
			n = (n << 1) | (int)bitAt(txt, i);
		return n;
	}
	
	private static long bitAt(String txt, int index)
	{
		return txt.charAt(index) == '1' ? 1l : 0l;
	}
	
	private static long bitAt(long n, int index)
	{
		return (n & (1l << index)) >>> index;
	}
	
	private static long maskL(int n)
	{
		return (1l << n) - 1;
	}
	
	private static int maskI(int n)
	{
		return (1 << n) - 1;
	}
	
	private long permute(String message, int[] permut)
	{
		long ret = 0;
		for(int i = 0; i < message.length(); ++i)
			ret = (ret << 1l) | bitAt(message, permut[i]);
		return ret;
	}
	
	private String permute(long message, int[] perInv)
	{
		StringBuilder sb = new StringBuilder();
		for(int i = 0; i < msgLen; ++i)
			sb.append(bitAt(message, msgLen - perInv[i] - 1));
		return sb.toString();
	}
	
	private long permute(long k, int[] p, int len)
	{
		long ret = 0;
		for(int i = 0; i < p.length; ++i)
			ret = (ret << 1) | bitAt(k, len - p[i] - 1);
		return ret;
	}
	
	private long expand(long a)
	{
		return permute(a, expandPerm, 32);
	}
	
	private long sbox(int a, int part)
	{
		int x = (a >> 1) & maskI(4);
		int y = (int) (bitAt(a, 5) * 2 + bitAt(a, 0));
		int index = y * 16 + x;
		return sbox[part][index];
	}
	
	private long sboxes(long a)
	{
		long ret = 0;
		for(int i = 0; i < 8; ++i)
		{
			ret = (ret << 4) | sbox((int) (a >> ((7 - i) * 6) & maskL(6)), i);
		}
		return ret;
	}
	
	private long f(long r, long roundKey)
	{
		r = expand(r);
		r ^= roundKey;
		r = sboxes(r);
		return permute(r, finalPerm, 32);
	}
	
	public String encrypt(String msg)
	{
		long m = permute(msg, initialPerm);
		
		long l = m >>> 32l;
		long r = m & 0xFFFFFFFFl;
		
		for(int i = 0; i < roundCount; ++i)
		{
			long pom = r;
			r = l ^ f(r, keys[i]);
			l = pom;
		}

		return permute((r << 32) + l, initialPermInv);
	}
	
	public String decrypt(String msg)
	{
		long m = permute(msg, initialPerm);
		
		long l = m >>> 32l;
		long r = m & 0xFFFFFFFFl;
		
		for(int i = roundCount - 1; i >= 0; --i)
		{
			long pom = r;
			r = l ^ f(r, keys[i]);
			l = pom;
		}
		
		return permute((r << 32) + l, initialPermInv);
	}
	
	public static void main(String[] args) {
		DES des = new DES("0001001100110100010101110111100110011011101111001101111111110001");
		System.out.println(des.encrypt("0000000100100011010001010110011110001001101010111100110111101111"));
		System.out.println(des.decrypt("1000010111101000000100110101010000001111000010101011010000000101"));
	}

}
